<template>
  <div>
    <div class="tool-box">
      <div class="logo-box">
        <h3>TPSHION</h3>
      </div>
      <div class="btn-box">
        <div class="title-box">
          项目：<el-input v-model="taskWork.name" placeholder="请输入项目名称" clearable style="width: 190px"/>
        </div>
        <div class="title-box">
          模版：
          <el-select v-model="selectTemplate" @change="templateChange" placeholder="选择模版">
            <el-option
              v-for="item in flowTemplateList"
              :key="item"
              :label="item"
              :value="item"
            >
              <span style="float: left">{{ item }}</span>
              <span @click.stop="handeleTemplate(item)" style="float: right; color: var(--el-text-color-secondary); font-size: 13px; color: #F56C6C;">删除</span>
            </el-option>
          </el-select>
        </div>
        <div class="opt-box">
          <el-button type="success" @click="clickSaveTemplate">保存为模版</el-button>
          <el-button type="primary" @click="saveClick">保存</el-button>
          <el-button type="warning" @click="backHome">退出</el-button>
        </div>
      </div>
    </div>
    <div class="stencil-app">
      <div class="app-stencil" id="stencil"></div>
      <div class="app-content" id="container"></div>
      <TeleportContainer />

      <!-- git -->
      <el-drawer v-model="drawerData.git" :direction="direction" size="35%" :with-header="false">
        <el-form :model="nodeData.git" label-position="top">
          <el-form-item label="节点id:" required>
            <el-input v-model="nodeData.git.id" readonly/>
          </el-form-item>
          <el-form-item label="节点类型:" required>
            <el-input v-model="nodeData.git.type" readonly/>
          </el-form-item>
          <el-form-item label="节点名称:" required>
            <el-input v-model="nodeData.git.title" maxlength="8" show-word-limit clearable/>
          </el-form-item>
          <el-form-item label="git仓库地址:" required>
            <el-input v-model="nodeData.git.data.url" type="textarea" maxlength="300" show-word-limit clearable/>
          </el-form-item>
          <el-form-item label="git密钥:" required>
            <el-select v-model="nodeData.git.data.secretName" clearable placeholder="请选择git密钥">
              <el-option
                v-for="(item,index) in gitSecretList"
                :key="index"
                :label="item.name"
                :value="item.name"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="默认分支:" required>
            <el-input v-model="nodeData.git.data.defaultBranch" clearable/>
          </el-form-item>
          <el-form-item label="选择分支:">
            <el-input v-model="nodeData.git.data.branch" clearable />
          </el-form-item>
        </el-form>
      </el-drawer>

      <!-- maven -->
      <el-drawer v-model="drawerData.maven" :direction="direction" size="35%" :with-header="false">
        <el-form :model="nodeData.maven" label-position="top">
          <el-form-item label="节点id:" required>
            <el-input v-model="nodeData.maven.id" readonly/>
          </el-form-item>
          <el-form-item label="节点类型:" required>
            <el-input v-model="nodeData.maven.type" readonly/>
          </el-form-item>
          <el-form-item label="节点名称:" required>
            <el-input v-model="nodeData.maven.title" maxlength="8" show-word-limit clearable />
          </el-form-item>
          <el-form-item label="执行命令:" required>
            <el-input v-model="nodeData.maven.data.command" clearable />
          </el-form-item>
          <el-form-item label="maven地址:">
            <el-input v-model="nodeData.maven.data.mavenHome" clearable />
          </el-form-item>
          <el-form-item label="pom文件地址:">
            <el-input v-model="nodeData.maven.data.pomPath" clearable />
          </el-form-item>
        </el-form>
      </el-drawer>

      <!-- docker -->
      <el-drawer v-model="drawerData.docker" :direction="direction" size="35%" :with-header="false">
        <el-form :model="nodeData.docker" label-position="top">
          <el-form-item label="节点id:" required>
            <el-input v-model="nodeData.docker.id" readonly/>
          </el-form-item>
          <el-form-item label="节点类型:" required>
            <el-input v-model="nodeData.docker.type" readonly/>
          </el-form-item>
          <el-form-item label="节点名称:" required>
            <el-input v-model="nodeData.docker.title" maxlength="8" show-word-limit clearable/>
          </el-form-item>
          <el-form-item label="镜像名称:" required>
            <el-input v-model="nodeData.docker.data.name" clearable/>
          </el-form-item>
          <el-form-item label="镜像标签:" required>
            <el-input v-model="nodeData.docker.data.tag" clearable/>
          </el-form-item>
          <el-form-item label="镜像仓库:">
            <el-input v-model="nodeData.docker.data.registry" clearable/>
          </el-form-item>
          <el-form-item label="dockerFile:" required>
            <el-input v-model="nodeData.docker.data.dockerFile" clearable/>
          </el-form-item>
          <el-form-item label="构建地址:" required>
            <el-input v-model="nodeData.docker.data.buildPath" clearable/>
          </el-form-item>
          <el-form-item label="ssh密钥:" required>
            <el-select v-model="nodeData.docker.data.secretName" clearable placeholder="请选择ssh密钥">
              <el-option
                v-for="(item,index) in sshSecretList"
                :key="index"
                :label="item.name"
                :value="item.name"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="构建前命令:" required>
            <el-input v-model="nodeData.docker.data.beforeCommand" type="textarea" maxlength="300" show-word-limit clearable/>
          </el-form-item>
          <el-form-item label="构建后命令:" required>
            <el-input v-model="nodeData.docker.data.afterCommand" type="textarea" maxlength="300" show-word-limit clearable/>
          </el-form-item>
          <el-form-item label="单例命令:" required>
            <el-input v-model="nodeData.docker.data.singleCommand" type="textarea" maxlength="300" show-word-limit clearable/>
          </el-form-item>
        </el-form>
      </el-drawer>

      <!-- k8s -->
      <el-drawer v-model="drawerData.k8s" :direction="direction" size="35%" :with-header="false">
        <el-form :model="nodeData.k8s" label-position="top">
          <el-form-item label="节点id:" required>
            <el-input v-model="nodeData.k8s.id" readonly/>
          </el-form-item>
          <el-form-item label="节点类型:" required>
            <el-input v-model="nodeData.k8s.type" readonly/>
          </el-form-item>
          <el-form-item label="节点名称:" required>
            <el-input v-model="nodeData.k8s.title" maxlength="8" show-word-limit/>
          </el-form-item>
        </el-form>
      </el-drawer>

      <!-- ssh -->
      <el-drawer v-model="drawerData.ssh" :direction="direction" size="35%" :with-header="false">
        <el-form :model="nodeData.ssh" label-position="top">
          <el-form-item label="节点id:" required>
            <el-input v-model="nodeData.ssh.id" readonly/>
          </el-form-item>
          <el-form-item label="节点类型:" required>
            <el-input v-model="nodeData.ssh.type" readonly/>
          </el-form-item>
          <el-form-item label="节点名称:" required>
            <el-input v-model="nodeData.ssh.title" maxlength="8" show-word-limit clearable />
          </el-form-item>
          <el-form-item label="ssh密钥:" required>
            <el-select v-model="nodeData.ssh.data.secretName" clearable placeholder="请选择ssh密钥">
              <el-option
                v-for="(item,index) in sshSecretList"
                :key="index"
                :label="item.name"
                :value="item.name"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="执行命令:" required>
            <el-input v-model="nodeData.ssh.data.command" type="textarea" maxlength="300" show-word-limit clearable/>
          </el-form-item>
          <el-form-item label="单例命令:" required>
            <el-input v-model="nodeData.ssh.data.singleCommand" type="textarea" maxlength="300" show-word-limit clearable/>
          </el-form-item>
        </el-form>
      </el-drawer>

      <!-- sftp -->
      <el-drawer v-model="drawerData.sftp" :direction="direction" size="35%" :with-header="false">
        <el-form :model="nodeData.sftp" label-position="top">
          <el-form-item label="节点id:" required>
            <el-input v-model="nodeData.sftp.id" readonly/>
          </el-form-item>
          <el-form-item label="节点类型:" required>
            <el-input v-model="nodeData.sftp.type" readonly/>
          </el-form-item>
          <el-form-item label="节点名称:" required>
            <el-input v-model="nodeData.sftp.title" maxlength="8" show-word-limit/>
          </el-form-item>
          <el-form-item label="ssh密钥:" required>
            <el-select v-model="nodeData.sftp.data.secretName" clearable placeholder="请选择ssh密钥">
              <el-option
                v-for="(item,index) in sshSecretList"
                :key="index"
                :label="item.name"
                :value="item.name"
              />
            </el-select>
          </el-form-item>
          <el-form-item label="远程地址:" required>
            <el-input v-model="nodeData.sftp.data.remotePath" type="textarea" maxlength="200" show-word-limit clearable/>
          </el-form-item>
          <el-form-item label="本地地址:" required>
            <el-input v-model="nodeData.sftp.data.localPath" type="textarea" maxlength="200" show-word-limit clearable/>
          </el-form-item>
          <el-form-item label="文件名称:">
            <el-input v-model="nodeData.sftp.data.fileName" type="textarea" maxlength="200" show-word-limit clearable/>
          </el-form-item>
          <el-form-item label="上传前命令:">
            <el-input v-model="nodeData.sftp.data.beforeCommand" type="textarea" maxlength="300" show-word-limit clearable/>
          </el-form-item>
        </el-form>
      </el-drawer>

      <!-- nodejs -->
      <el-drawer v-model="drawerData.nodejs" :direction="direction" size="35%" :with-header="false">
        <el-form :model="nodeData.nodejs" label-position="top">
          <el-form-item label="节点id:" required>
            <el-input v-model="nodeData.nodejs.id" readonly/>
          </el-form-item>
          <el-form-item label="节点类型:" required>
            <el-input v-model="nodeData.nodejs.type" readonly/>
          </el-form-item>
          <el-form-item label="节点名称:" required>
            <el-input v-model="nodeData.nodejs.title" maxlength="8" show-word-limit/>
          </el-form-item>
          <el-form-item label="执行命令:" required>
            <el-input v-model="nodeData.nodejs.data.command" type="textarea" maxlength="300" show-word-limit clearable/>
          </el-form-item>
        </el-form>
      </el-drawer>

      <!-- gradle -->
      <el-drawer v-model="drawerData.gradle" :direction="direction" size="35%" :with-header="false">
        <el-form :model="nodeData.gradle" label-position="top">
          <el-form-item label="节点id:" required>
            <el-input v-model="nodeData.gradle.id" readonly/>
          </el-form-item>
          <el-form-item label="节点类型:" required>
            <el-input v-model="nodeData.gradle.type" readonly/>
          </el-form-item>
          <el-form-item label="节点名称:" required>
            <el-input v-model="nodeData.gradle.title" maxlength="8" show-word-limit/>
          </el-form-item>
          <el-form-item label="执行命令:" required>
            <el-input v-model="nodeData.gradle.data.command" type="textarea" maxlength="300" show-word-limit clearable/>
          </el-form-item>
        </el-form>
      </el-drawer>

      <!-- oss -->
      <el-drawer v-model="drawerData.oss" :direction="direction" size="35%" :with-header="false">
        <el-form :model="nodeData.oss" label-position="top">
          <el-form-item label="节点id:" required>
            <el-input v-model="nodeData.oss.id" readonly/>
          </el-form-item>
          <el-form-item label="节点类型:" required>
            <el-input v-model="nodeData.oss.type" readonly/>
          </el-form-item>
          <el-form-item label="节点名称:" required>
            <el-input v-model="nodeData.oss.title" maxlength="8" show-word-limit/>
          </el-form-item>
          <el-form-item label="文件地址:" required>
            <el-input v-model="nodeData.oss.data.path" type="textarea" maxlength="300" show-word-limit clearable/>
          </el-form-item>
        </el-form>
      </el-drawer>
    </div>
  </div>
</template>

<script setup lang="ts">
import { Graph, Node } from '@antv/x6'
import { onMounted, ref } from 'vue'
import { Stencil } from '@antv/x6-plugin-stencil'
import { Snapline } from '@antv/x6-plugin-snapline'
import { Selection } from '@antv/x6-plugin-selection'
import { register, getTeleport} from '@antv/x6-vue-shape'
import TaskNode from '../components/TaskNode.vue'
import { NodeData } from '../model/NodeModel'
import { nodeTemplateList } from "../utils/NodeTemplateData";
import { saveFlowData, getFlowData } from '../api/TaskApi'
import { TaskWork } from "../model/TaskModel";
import { ElMessage, ElLoading, ElMessageBox } from 'element-plus'
import { useRouter, useRoute } from 'vue-router'
import { saveTemplate, templateList, getTemplate, deleteTemplate } from "../api/FlowApi";
import { SecretData } from '../model/SecretModel'
import { secretList } from '../api/SecretApi'

const router = useRouter()
const route = useRoute()
let taskId = route.query.taskId as string

register({
  shape: 'task-node',
  width: 110,
  height: 40,
  component: TaskNode,
  ports: {
    groups: {
      left: {
        position: 'left',
        attrs: {
          circle: {
            magnet: true,
            stroke: '#8f8f8f',
            r: 5,
          },
        },
      },
      right: {
        position: 'right',
        attrs: {
          circle: {
            magnet: true,
            stroke: '#8f8f8f',
            r: 5,
          },
        },
      },
    },
  },
})

const TeleportContainer = getTeleport()

const commonPorts = {
  items: [
    {
      group: 'left',
    },
    {
      group: 'right',
    }
  ],
}

const buildGraph = () => {
  return new Graph({
    container: document.getElementById('container') as HTMLElement,
    autoResize: true,
    background: {
      color: '#F2F7FA',
    },
    grid: {
      visible: true,
      type: 'doubleMesh',
      args: [
        {
          color: '#eee', // 主网格线颜色
          thickness: 1, // 主网格线宽度
        },
        {
          color: '#ddd', // 次网格线颜色
          thickness: 1, // 次网格线宽度
          factor: 4, // 主次网格线间隔
        },
      ],
    },
    panning: true,
    mousewheel: true,
    connecting: {
      allowBlank: false,
      allowNode: false, // boolean
      allowMulti: false,
      allowLoop: false,
      allowEdge: false,
      allowPort: true,
      highlight: true,
      createEdge() {
        return this.createEdge({
          attrs: {
            line: {
              stroke: '#8f8f8f',
              strokeWidth: 1,
            },
          },
        })
      },
      validateMagnet({ magnet }) {
        // 节点上方的连接桩无法创建连线
        return magnet.getAttribute('port-group') === 'right'
      },
      validateConnection({
        sourceCell,
        targetCell,
        sourceMagnet,
        targetMagnet,
      }) {
        // 不能连接自身
        if (sourceCell === targetCell) {
          return false
        }

        // 只能从 bottom 连接桩开始连接，连接到 top 连接桩
        if (!sourceMagnet || sourceMagnet.getAttribute('port-group') === 'left') {
          return false
        }

        if (!targetMagnet || targetMagnet.getAttribute('port-group') !== 'left') {
          return false
        }

        // 不能重复连线
        // const edges = this.getEdges()
        // const portId = targetMagnet.getAttribute('port')
        // if (edges.find((edge) => edge.getTargetPortId() === portId)) {
        //   return false
        // }

        return true
      },
    },
    highlighting: {
      // 连接桩可以被连接时在连接桩外围围渲染一个包围框
      magnetAvailable: {
        name: 'stroke',
        args: {
          attrs: {
            fill: '#fff',
            stroke: '#A4DEB1',
            strokeWidth: 4,
          },
        },
      },
      // 连接桩吸附连线时在连接桩外围围渲染一个包围框
      magnetAdsorbed: {
        name: 'stroke',
        args: {
          attrs: {
            fill: '#fff',
            stroke: '#31d0c6',
            strokeWidth: 4,
          },
        },
      }
    },
  })
}

const buildStencil = (graph: Graph) => {
  return new Stencil({
      title: '任务节点',
      target: graph,
      stencilGraphHeight: 0,
      stencilGraphWidth: 250,
      groups: [
        {
          name: 'group1',
          title: 'Group',
          collapsable: false
        },
      ],
      layoutOptions:{
        columns: 1,
        dx: 60
      }
    })
}

const drawerData = ref({
  git: false,
  maven: false,
  docker: false,
  k8s: false,
  ssh: false,
  sftp: false,
  nodejs: false,
  gradle: false,
  oss: false
})

const direction = ref('rtl')

const nodeData = ref({
  git: {} as NodeData,
  maven: {} as NodeData,
  docker: {} as NodeData,
  k8s: {} as NodeData,
  ssh: {} as NodeData,
  sftp: {} as NodeData,
  nodejs: {} as NodeData,
  gradle: {} as NodeData,
  oss: {} as NodeData
})

const taskWork = ref<TaskWork>({
  name: '',
  taskId: '',
  nodeDataList:[],
  flowData: ''
})

const flowTemplateList = ref<string[]>([])
const selectTemplate = ref<string>()
const gitSecretList = ref<SecretData[]>([])
const sshSecretList = ref<SecretData[]>([])

let graph:Graph

onMounted(() => {
  initWork()
})

const initSecretList = async () => {
  const rs = await secretList()
  if(rs.data){
    gitSecretList.value = rs.data.filter(i => i.type === 'git')
    sshSecretList.value = rs.data.filter(i => i.type === 'ssh')
  }
}

/**
 * 设置当前点击节点数据
 * @param node 当前点击节点
 */
const setCurrentNodeData = (node:Node) => {
  const data = nodeData.value
  if(!data){
    return
  }
  if(node.data.type == 'git'){
    data.git = node.data
    data.git.id = node.id
    drawerData.value.git = true
  }
  if(node.data.type == 'maven'){
    data.maven = node.data
    data.maven.id = node.id
    drawerData.value.maven = true
  }
  if(node.data.type == 'docker'){
    data.docker = node.data
    data.docker.id = node.id
    drawerData.value.docker = true
  }
  if(node.data.type == 'k8s'){
    data.k8s = node.data
    data.k8s.id = node.id
    drawerData.value.k8s = true
  }
  if(node.data.type == 'ssh'){
    data.ssh = node.data
    data.ssh.id = node.id
    drawerData.value.ssh = true
  }
  if(node.data.type == 'sftp'){
    data.sftp = node.data
    data.sftp.id = node.id
    drawerData.value.sftp = true
  }
  if(node.data.type == 'nodejs'){
    data.nodejs = node.data
    data.nodejs.id = node.id
    drawerData.value.nodejs = true
  }
  if(node.data.type == 'gradle'){
    data.gradle = node.data
    data.gradle.id = node.id
    drawerData.value.gradle = true
  }
  if(node.data.type == 'oss'){
    data.oss = node.data
    data.oss.id = node.id
    drawerData.value.oss = true
  }
}

/**
 * 初始化工作空间
 */
const initWork = async () => {
  await initTaskWork(taskId)
  await initGraph()
  await initStencil()
  await initGraphEvent()
  await initTemplateList()
  await initSecretList()
}

const initTemplateList = async () => {
  const rs = await templateList()
  if(rs.data){
    flowTemplateList.value = rs.data
  }else{
    flowTemplateList.value = []
  }
}

/**
 * 初始化任务数据
 * @param taskId 任务id
 */
const initTaskWork = async (taskId:string) => {
  if(!taskId){
    return
  }
  const loading = getLoading('流程数据加载中...')
  const res = await getFlowData(taskId)
  loading.close()
  if(res.data){
    taskWork.value = res.data
  }
}

/**
 * 初始化画板
 */
const initGraph = async () => {
  graph = buildGraph()

  if(taskWork.value.flowData){
    graph.fromJSON(JSON.parse(taskWork.value.flowData))
    graph.getNodes().forEach(i => {
      i.data.status = 'init'
    })
  }
  
  graph.centerContent()
  graph.use(
    new Snapline({
      enabled: true,
    })
  )
  graph.use(
    new Selection({
      enabled: true,
      multiple: false,
      rubberband: true,
      movable: false,
      showNodeSelectionBox: true,
      pointerEvents: 'none',
      modifiers: 'shift'
    })
  )
}

/**
 * 初始化画板事件
 */
const initGraphEvent = async () => {
  graph.on('node:mouseenter', ({ node }) => {
    node.addTools([
      {
        name: 'boundary',
        args: {
          attrs: {
            fill: '#ffffff',
            stroke: '#333',
            'stroke-width': 1,
            'fill-opacity': 0.2,
          },
        },
      },
      {
        name: 'button-remove',
        args: {
          x: 0,
          y: 0,
          offset: { x: 110, y: 1 },
        },
      },
    ])
  })

  graph.on('node:mouseleave', ({ node }) => {
    node.removeTools()
  })

  graph.on('edge:click', ({ edge }) => {
    if(edge.hasTools()){
      edge.removeTools()
      return
    }

    edge.addTools([
      {
        name: 'button-remove',
      },
    ])
  })

  graph.on('edge:unselected', ({ edge }) => {
    if(edge.hasTools()){
      edge.removeTools()
    }
  })

  graph.on('node:click', ({ node }) => {
    setCurrentNodeData(node)
  })
}

/**
 * 初始化节点模版
 */
const initStencil = async () => {
  const stencil = buildStencil(graph)
  const stencilDom = document.getElementById('stencil') as HTMLElement
  stencilDom.appendChild(stencil.container)

  const nodes: Node[] = []

  nodeTemplateList.forEach(i => {
    const node = graph.createNode({
      shape: 'task-node',
      ports: commonPorts,
      data: i
    });
    nodes.push(node)
  })

  stencil.load(nodes, 'group1')
}

const saveClick = async () => {
  if(!taskWork.value.name){
    ElMessage.error('请先输入项目名称')
    return
  }

  const data = graph.toJSON({ diff: true })
  if(data.cells.length <= 0){
    ElMessage.error('没有流程节点')
    return
  }

  graph.getNodes().forEach(i => i.data.id = i.id)

  const rootNodes:Node[] = graph.getRootNodes()
  if(rootNodes.length > 1){
    ElMessage.error('只能有一个根节点')
    return
  }

  const rootNode: Node = rootNodes[0]
  const list: NodeData[] = []
  list.push(rootNode.data)

  if(graph.getNeighbors(rootNode).length > 1){
    ElMessage.error('暂不支持多分支')
    return
  }

  const childNodes = graph.getSuccessors(rootNode, { deep: true }) as Node[]
  if(childNodes){
    childNodes.forEach(i => list.push(i.data))
  }

  taskWork.value.flowData = JSON.stringify(data)

  taskWork.value.nodeDataList = list

  const loading = getLoading('正在保存数据...')
  const rs = await saveFlowData(taskWork.value)
  loading.close()
  taskWork.value = rs.data
  ElMessage.success('保存成功')
}

const getLoading = (msg:string):any  =>{
  return ElLoading.service({
    lock: true,
    text: msg,
    background: 'rgba(0, 0, 0, 0.7)',
  })
}

const backHome = () => {
  router.push({path: '/'})
}

const clickSaveTemplate = () => {
  ElMessageBox.prompt('请输入模版名称', '保存模版', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
  }).then(async ({ value }) => {
    if(!value){
      ElMessage.error("请输入模版名称")
      return
    }

    const data = graph.toJSON({ diff: true })
    if(data.cells.length <= 0){
      ElMessage.error('没有流程节点')
      return
    }

    const param = {} as TaskWork

    param.flowData = JSON.stringify(data)

    param.nodeDataList = []
    graph.getNodes().forEach(i => {
      param.nodeDataList.push(i.data)
    })

    await saveTemplate(value, param)
    ElMessage.success('保存模版成功')
    await initTemplateList()
  }).catch(() => {
    
  })
}

const templateChange = async () => {
  if(selectTemplate.value){
    const rs = await getTemplate(selectTemplate.value)
    taskWork.value = rs.data
    graph.fromJSON(JSON.parse(taskWork.value.flowData))
    graph.getNodes().forEach(i => {
      i.data.status = 'init'
    })
    graph.centerContent()
  }
}

const handeleTemplate = async (name:string) => {
  if(name){
    await deleteTemplate(name)
    await initTemplateList()
    if(selectTemplate.value == name){
      selectTemplate.value = ''
    }
  }
}

</script>

<style lang="less" scoped>

.tool-box{
  height: 60px;
  background-color: #7a7fd3;
  display: flex;
  align-items: center;
  justify-content: end;

  .logo-box{
    width: 100px;
    height: 60px;
    color: #ffffff;
    position: absolute;
    left: 15px;
  }

  .btn-box{
    height: 60px;
    padding-left: 10px;
    padding-right: 10px;
    display: flex;
    align-items: center;

    .title-box{
      width: 250px;
      height: 60px;
      color: #ffffff;
      display: flex;
      align-items: center;
    }

    .opt-box{
      height: 60px;
      display: flex;
      align-items: center;
    }
  }
}

.stencil-app {
  height: calc(100vh - 60px);
  display: flex;
  padding: 0;
  font-family: sans-serif;

  .app-stencil {
    position: relative;
    width: 250px;
    border: 1px solid #ede6e6;
  }

  .app-content {
    flex: 1;
    height: calc(100vh - 60px);
  }
}
</style>